# https://blog.csdn.net/ClushioAqua/article/details/129834114【已失效】
# 注意【可安装requests==2.32.3;beautifulsoup4==4.12.3;】
# 注意【oid.txt改名时，upname会有错误，所以get_info取消upname】

import requests
from bs4 import BeautifulSoup
import re
import json
from pprint import pprint
import os,time
# 【案例：https://www.bilibili.com/video/BV1Di421h7D4/?spm_id_from=333.1365.list.card_archive.click&vd_source=572cae486d1c7a35b1a384b822b1681d】
# 注意【1新网页头，2子评论，需要定义pnmax才能跳出循环，3、评论和回复追加句号】                    
from tkinter import Tk,Entry, Button,filedialog
# 注意【打开文件，读取txt,生成list，逐一爬取，输出有误时的列表】

def trans_date(v_timestamp):
    """10位时间戳转换为时间字符串"""
    timeArray = time.localtime(v_timestamp)
    otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S", timeArray)
    return otherStyleTime

# 新网页头：main.json，请求标头
headers={
    "User-Agent" :"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0",
    "referer" : "https://www.bilibili.com/",
    "authority": 'api.bilibili.com',
    "cookie":"buvid3=A51ED3C7-D13D-5C0B-D7DC-A5E448B3163B51919infoc; b_nut=1722411051; _uuid=6CAD945E-C9CF-937E-9EDA-67B8B710A969B56081infoc; hit-dyn-v2=1; buvid4=2C551FEF-066A-CDC8-FBC2-69CB47FE560554250-024073107-MxInw9SAM4Ie2TOnH7PLjDWO%2Fuow%2FL6ee5w896VgLRF7c4%2BSSw2LMBzFw50%2BMz6V; fingerprint=ca16129818e01703a0548a155f721286; buvid_fp_plain=undefined; SESSDATA=a0ba7c7d%2C1737963156%2C9bcdb%2A71CjAHB26CW9KyEx4VKf8Qzdcm_fW_UgNgR0Vo_vOklTY4OCmqSJHDF9NkU_OLW09gCuESVkgwaVVBcERXSXBEazRpdVQ5cUZGbzJCaEF5RDZEb3NIdXlYX0hHSmFWN1F3dVVpbVdDczdzbDlxUUg5NjQxSFdLZ0VNQUVBRmtFUzA4b2N3Qm16Mk53IIEC; bili_jct=fc8a84b74eca540665ed7bb11c214199; DedeUserID=3546626710833234; DedeUserID__ckMd5=e9c3205bd844abd3; sid=7p1hgwb3; CURRENT_FNVAL=4048; rpdid=0z9ZwfQmLZ|eZl3Mpib|49N|3w1Sz3Ps; LIVE_BUVID=AUTO2817225990369672; header_theme_version=CLOSE; enable_web_push=DISABLE; home_feed_column=5; bsource=search_bing; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MjQyMjIzMTAsImlhdCI6MTcyMzk2MzA1MCwicGx0IjotMX0.ifw53zEo4PK4GVqWwQtLSPC4GeYU4koDKaOonOmsjPA; bili_ticket_expires=1724222250; buvid_fp=6e66690b44a29b07dce822b319c28af3; browser_resolution=1528-840; PVID=2; b_lsid=8D9CE1010D_1916F74F8B6; bp_t_offset_3546626710833234=967726444698402816",
    }

def get_info(headers,burl):#,oid):
# 删减视频地址
#     url=burl.split('?', 1)[0]# 用？分割后的第一个元素【列表得到的https://www.bilibili.com/video/BV1hS4y1x7Ys/】
    url=burl
    print('到这步6')
    
    html_text=requests.get(url=url,headers=headers).text
#     test oid
#     print(html_text)
#     file.write(html_text)
    try:
#         if re.findall(r"&oid=(\d+)",html_text)[0] is not None :
#     oid=re.findall(r' "aid":(\d+),',html_text)[0]
        oid=re.findall(r"&oid=(\d+)",html_text)[0]
        print(oid)
#         else:
#             print('&oid=方法失败，用aid:方法')
#             oidlist=re.findall(r'aid":(.*?),',html_text)
#             oid=oidlist[0]
#             print(oid)
    except Exception as e:
        print(e)
        print('&oid=方法失败，用aid:方法')
        oidlist=re.findall(r'aid":(.*?),',html_text)
        oid=oidlist[0]
        print(oid)
#         exit()

    soup=BeautifulSoup(html_text, 'html.parser')
    try:
# <h1 data-title="微流控应用：微滴/微球制备仪制备海藻酸钠微球(乙酸固化法)" title="微流控应用：微滴/微球制备仪制备海藻酸钠微球(乙酸固化法)"         
        title=soup.find('h1',class_="video-title").text
        print(title)#标题
# <div class="pubdate-ip-text" data-v-aed3e268="">2024-03-26 10:04:54</div>                                                                               
        bdate=soup.find('div',class_="pubdate-ip-text").text
    #     print(bdate)#发布时间
# <div class="upname"><span class="name">FluidicLab微流控实验室</span>                                          
#         upname=soup.find('div',class_="upname").text#【打开目录已知博主】
    #     print(upname)#博主
    except Exception as e:
        print("get_info失败："+e)
        
# 简介可能没有
    if soup.find('span',class_="desc-info-text") !=None:
        abstr=soup.find('span',class_="desc-info-text").text
#         file.write("地址："+url+"\n"+"标题："+title+"\n"+"时间："+bdate+"\n"+"简介："+abstr+"\n"+"博主："+upname+str(oid)+"\n")
        file.write("地址："+url+"\n"+"标题："+title+"\n"+"时间："+bdate+"\n"+"简介："+abstr+"\n"+str(oid)+"\n")
    else:
#         file.write("地址："+url+"\n"+"标题："+title+"\n"+"时间："+bdate+"\n"+"简介：无"+"\n"+"博主："+upname+str(oid)+"\n")
        file.write("地址："+url+"\n"+"标题："+title+"\n"+"时间："+bdate+"\n"+"简介：无"+"\n"+str(oid)+"\n")
#     print(abstr)

#     return oid,upname,title
    return oid,bdate,title

def get_SecondReply(headers,root,pnmax,oid):
    oid=oid
    pn = 1
    while True:
        # pn 页，root来自每个根评论的rpid【获取子评论】
        URL = (f"https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&pn={pn}&type=1&oid={oid}&ps=10&root={root}")#模板
# replies?csrf   https://api.bilibili.com/x/v2/reply/reply?                          &oid=470113786&pn=1&ps=10&root=117316316480&type=1
#         URL = (f"https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&pn={pn}&type=1&oid=824175427&ps=10&root={root}&_=1679992607971")#root是根评论的root
        # file.write('\n'+str({pn})+'、子评论：'+URL+ '\n')#写入：{1}、子评论：https://api.bilibili.com/x/v2/reply/reply?jsonp=jsonp&pn=1&type=1&oid=1456315203&ps=10&root=238978101456
        respond = requests.get(URL , headers = headers) # 获得源代码 抓包
        reply_num = 0
        if(respond.status_code == 200):#【其实原因就是pn超额仍能爬到网页，那需要获取pn的总数？？】——根评论1（main.json》1的reply数）
#             main.json>data>replies>0>rcount=73,reply_control>sub_reply_entry_text=共73条回复，即1-8页？                                                                                                                                         
            respond.encoding = "UTF-8"
            html = respond.text
            json_html = json.loads(html)
            
            if json_html['data']['replies'] is None:
                if(pn == 1):
                    return 0
                else :
                    return 1
            
            for i in range(0,len(json_html['data']['replies'])):
                if json_html['data']['replies'] is None:
                    break
                reply = json_html['data']['replies'][reply_num]['content']['message']
                uname=json_html['data']['replies'][reply_num]['member']['uname']#加入用户名
                reply = reply.replace('\n',',')
                ctime=json_html['data']['replies'][reply_num]['ctime']
#                 print(trans_date(ctime))
                print(uname+"》: "+reply)
                reply_num += 1
                file.write("  -"+str(pn)+"-"+str(reply_num)+"、" +uname+"》: "+reply+ "。 "+trans_date(ctime)+'\n')#根评论1的所有子评论，按页，1-1,,1-10,,,8-5,说明成功进入get_secondreply()
            pn += 1
            print(pn)#【无法跳出循环，pn一直在增加！！！】                          
            time.sleep(0.5)
            if pn>=pnmax:#pn从1开始，共8页，pnmax=9时，跳出循环
                break                          
        else:
            print("爬Secondreply error!")
            exit(-1)
 
    
def get_rootReply(headers,oid):
    oid=oid
    num = 1
    replay_index = 1#根评论的索引replay_index
    while True:

#         【获取根评论】
        URL = (f"https://api.bilibili.com/x/v2/reply/main?&jsonp=jsonp&next={num}&mode=3&oid={oid}&plat=1&type=1")#根评论模板
#         URL = (f"https://api.bilibili.com/x/v2/reply/main?&jsonp=jsonp&next={num}&type=1&oid=470113786&mode=3&plat=1&_=1680096302818")   #获得网页源码
        # file.write('\n'+str({num})+'、根评论：'+URL+ '\n')#写入：{1}、根评论：https://api.bilibili.com/x/v2/reply/main?&jsonp=jsonp&next=1&mode=3&oid=1456315203&plat=1&type=1
        # print('\n'+str({num})+'、根评论：'+URL+ '\n')
        respond = requests.get(URL , headers = headers) # 获得源代码 抓包
#         print(respond.status_code)#200
        reply_num = 0
        if(respond.status_code == 200): # 如果响应为200就继续，否则退出
            respond.encoding = "UTF-8"
            html = respond.text
            json_html = json.loads(html)    # 把格式转化为json格式 一个是好让pprint打印，一个是好寻找关键代码
#             print(json_html)
            if json_html['data']['replies'] is None or len(json_html['data']['replies']) == 0 :
                break
#             print(len(json_html['data']['replies']))
#             print('这一步1')
            for i in range(0,len(json_html['data']['replies'])):   #一页只能读取20条评论
                rcount=json_html['data']['replies'][reply_num]['rcount']#根评论的总回复数，每页10条，rcount//10+1=pn总页数[//向下取整]
                print("共"+str(rcount)+"条回复")#，注意[]内的''
                pnmax=int(rcount)//10+1
                # print(pnmax)
                reply = json_html['data']['replies'][reply_num]['content']['message']                                                                     
                root = json_html['data']['replies'][reply_num]['rpid']# 【根评论的rpid==root，传给子评论】
                uname=json_html['data']['replies'][reply_num]['member']['uname']#加入用户名
                reply = reply.replace('\n',',')
                ctime=json_html['data']['replies'][reply_num]['ctime']
#                 print(trans_date(ctime))
#                 print('根评论')
                print("根评论-"+str(replay_index) + '.'+uname+"》: "+reply)#根评论的索引replay_index
                file.write(str(replay_index) + '、' + uname+"》: "+reply +"。 "+trans_date(ctime)+ '\n')
                if json_html['data']['replies'][reply_num]['replies'] is not None:
                    # print(get_SecondReply(headers,root,pnmax,oid))#无效
#                     print(len(json_html['data']['replies'][reply_num]['replies']))#返回1
#                     print('这一步2')

                    if(get_SecondReply(headers,root,pnmax,oid) == 0):#【这一行无效，注释掉才能正常运行】【实际pn一直增加】
#                     get_SecondReply(headers,root,oid)
                        for i in range(0,len(json_html['data']['replies'][reply_num]['replies'])):
                            reply = json_html['data']['replies'][reply_num]['replies'][i]['content']['message']
                            uname=json_html['data']['replies'][reply_num]['member']['uname']#加入用户名
                            reply = reply.replace('\n',',')
                            print(str(num)+"--"+str(reply_num)+uname+"》: "+reply)
                            print('这一步3')
                            file.write(str(num)+"--"+str(reply_num)+"    " +uname+"》: "+ reply + '\n')#【这几行无效？？】
                            
                reply_num += 1 
                replay_index += 1
            num += 1
            
            time.sleep(0.5)
        else :
            print("respond error!")
            break
    file.close()


#【0827tk-listbox+entry+button-模板】
def open_file(txt_path):
    url_list=[]
#     file_path = filedialog.askopenfilename(filetypes=[("txt files", "*.txt")])
#         print(file_path)
#     print(os.path.basename(file_path))
#     if file_path:  #如果存在,
# #         self.txt_file = file_path
# #         text_box_filename.delete(0, tk.END)  
#         text_box_filename.insert(index=0, string=file_path)

# listbox显示txt内容：方法二【0827xlwt-txt转xls-多行多列】
    f=open(txt_path,encoding='utf-8')
    lines = f.readlines()
    for index, line in enumerate(lines):  
        # 去除行尾的换行符  
        line = line.strip()
        parts=line.split('\t')[0]#文本
        # listbox1.insert(tk.END, parts + '\n')  
        childparts=parts.split('----')#生成list[]
        url_list.append( childparts[0])  # URL
#         print(childparts[0])
    return url_list

# # tk创建界面
# root=Tk()
# root.title('导入txt,爬取评论')
# root.geometry("+900+300")#定位0×0+900+300窗口设置为300x100的尺寸,与屏幕最左边相隔20像素,与屏幕最上方相隔50像素
# root.attributes('-topmost', True) # note - before topmost【将其设置为最高(但它将始终位于其他位置的前面)
# 
# #创建单行文本框
# text_box_filename = Entry(root, width=40)#entry单行输入框，text多行输入框
# text_box_filename.grid(row=0, column=0, sticky='ew')#sticky="nsew")左右拉满
# 
# #创建按钮
# btn_open_file=Button(root, text="打开文件", command=open_file)
# btn_open_file.grid(row=0, column=1, sticky="ew")

# root.mainloop()不会运行后续

if __name__ == '__main__':
#     root.mainloop()不会运行后续
    txt_path = filedialog.askopenfilename(filetypes=[("txt files", "*.txt")])
    try:
        url_list=open_file(txt_path)#[0]#【如果两行open_file()，会打开两次】
#         os.path.abspath(os.path.join(path, ".."))#获取上级目录
        txt_folder=txt_path.replace('.txt','')
        print('创建同名目录')#
        if not os.path.exists(txt_folder):
            os.makedirs(txt_folder)#创建同级目录
#         new_file_path=os.path.join(txt_folder,'oid.txt')#【失败】默认是\
        new_file_path=txt_folder+'/oid.txt'
        print(new_file_path)#E:/PCpackage/thonnyprj/requestsEX/bili/hongniuhouke10724361\oid.txt#【创建文件夹失败】
#         file=open(new_file_path,'w',encoding='utf-8')
#         file.write('nihao')
#         file.close()
#         print('到这步2')
        fail_list=[]
    except :
        exit()
    for i in range(len(url_list)):
        time.sleep(2)
        burl=url_list[i]
        print(burl)
#         print('到这步3')
        try:
            print('到这步4')#成功过
            file=open(new_file_path,'w',encoding='utf-8')
#             file=open(f'E:\\bili\\oid.txt', 'w',encoding='utf-8')
#             oid,upname,title=get_info(headers,burl)
            oid,bdate,title=get_info(headers,burl)
            print('到这步5')#失败
            print(oid)
            print(bdate)
            print(title)
            get_rootReply(headers,oid)
            newtitle=title.replace("|","-").replace("/","-")
            new_file_path1=new_file_path.replace(f'oid.txt',f'{bdate}+{newtitle}+{oid}.txt')#其实是{}无法传递进去
            print(new_file_path1)
#             os.rename(new_file_path,new_file_path.replace('oid.txt','{upname}+{newtitle}+{oid}.txt'))#【出错，无法创建】
            os.rename(new_file_path,new_file_path1)
#             os.rename(f'E:\\bili\\oid.txt',f'E:\\bili\\{2}+{upname}+{newtitle}+{oid}.txt')
        except Exception as e:
            print(burl+'————出错')
            print(e)#an only concatenate str (not "IndexError") to str
            fail_list.append(burl)
    print('失败网址')
    print(fail_list)
    print("********************结束********************")
#     root.mainloop()#如果写在里面或上面，都会不执行后续    
     
#     burl=input("复制粘贴视频地址：")
#     # burl="https://www.bilibili.com/video/BV1wj421d7GF/"#微流控
# #     burl='https://www.bilibili.com/video/BV1Di421h7D4/?spm_id_from=333.1365.list.card_archive.click&vd_source=572cae486d1c7a35b1a384b822b1681d'#黄金眼
#     file = open(f'E:\\bili\\oid.txt', 'w',encoding='utf-8')
#     oid,upname,title=get_info(headers,burl)
#     get_rootReply(headers,oid)
#     newtitle=title.replace("|","-").replace("/","-")
#     os.rename(f'E:\\bili\\oid.txt',f'E:\\bili\\{2}+{upname}+{newtitle}+{oid}.txt')
#     print("********************成功********************")
    